Skip to content

Add support for Java version-specific JVM options#597

Merged
xerial merged 14 commits intomainfrom
feature/java-version-specific-opts
Sep 4, 2025
Merged

Add support for Java version-specific JVM options#597
xerial merged 14 commits intomainfrom
feature/java-version-specific-opts

Conversation

@xerial
Copy link
Copy Markdown
Owner

@xerial xerial commented Sep 4, 2025

Summary

  • Added packJvmVersionSpecificOpts setting to configure JVM options per Java version
  • Updated launch scripts to detect Java version and apply appropriate options
  • Added test case for the new feature

Motivation

Applications often need to use different JVM options depending on the Java version being used. For example:

  • Java 8 requires -XX:MaxPermSize which was removed in Java 9+
  • Java 11+ supports experimental JVM features like JVMCI
  • Java 17+ supports newer garbage collectors like ZGC
  • Java 21+ supports generational ZGC

This PR allows developers to configure version-specific JVM options that will be automatically applied based on the Java version detected at runtime.

Usage Example

packJvmVersionSpecificOpts := Map(
  "myapp" -> Map(
    8 -> Seq("-XX:MaxPermSize=256m"),
    11 -> Seq("-XX:+UnlockExperimentalVMOptions", "-XX:+UseJVMCICompiler"),
    17 -> Seq("-XX:+UseZGC"),
    21 -> Seq("-XX:+UseZGC", "-XX:+GenerationalZGC")
  )
)

Implementation Details

  • The launcher scripts now include Java version detection logic (based on sbt's implementation)
  • Version-specific options are added to JAVA_OPTS after version detection
  • Works for both Unix/Linux and Windows batch scripts
  • Maintains backward compatibility - existing configurations continue to work unchanged

Test Plan

  • Added scripted test for the new feature
  • Verified existing tests pass
  • Tested with different Java versions locally

🤖 Generated with Claude Code

Added packJvmVersionSpecificOpts setting to configure JVM options per Java version.
This allows users to specify different JVM options based on the Java version being used.

Example usage:
```scala
packJvmVersionSpecificOpts := Map(
  "myapp" -> Map(
    8 -> Seq("-XX:MaxPermSize=256m"),
    11 -> Seq("-XX:+UseJVMCICompiler"),
    17 -> Seq("-XX:+UseZGC")
  )
)
```

The launcher scripts now detect the Java version at runtime and apply the appropriate
JVM options automatically. This is useful for applications that need to support
multiple Java versions with different optimal JVM settings.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Changes

Hello @xerial, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request enhances the application packaging process by enabling dynamic application of JVM options tailored to the Java runtime environment. It addresses the common need for different JVM configurations across various Java versions (e.g., handling deprecated options or leveraging new features), ensuring that packaged applications can run optimally and without compatibility issues on diverse Java setups. The change provides a flexible mechanism for developers to define these version-specific settings, which are then automatically incorporated into the generated launch scripts.

Highlights

  • New Setting for JVM Options: Introduced packJvmVersionSpecificOpts to allow configuring JVM options that are applied based on the detected Java version.
  • Dynamic JVM Option Application: Updated launch scripts (for both Unix/Linux and Windows) to detect the Java runtime version and automatically apply the corresponding version-specific JVM options.
  • Comprehensive Testing: Added a new scripted test case to validate the functionality of version-specific JVM options across different Java versions.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a valuable feature for specifying Java version-specific JVM options. The implementation is generally well-structured. However, I've identified a few areas for improvement, primarily concerning the portability of the generated launcher scripts. The shell script for Unix-like systems uses bash-specific syntax that will fail on POSIX-compliant shells like dash, and the Windows batch script uses a non-standard JVM option. Additionally, there's a minor point about using a deprecated library in the new test code. Addressing these points will make the feature more robust and reliable across different environments.

xerial and others added 2 commits September 4, 2025 13:06
- Made Unix launch script POSIX-compliant by replacing bash-specific syntax
  - Replaced [[ ]] with case statement for pattern matching
  - Removed use of BASH_REMATCH and regex operators
  - Removed local keyword for better shell compatibility

- Fixed Windows batch script to use standard -version instead of -fullversion
  - Ensures compatibility with all JVM implementations

- Updated test to use non-deprecated scala.jdk.CollectionConverters
  - Replaced deprecated scala.collection.JavaConverters import

These changes improve portability across different shell environments and
JVM implementations while maintaining the same functionality.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Added documentation and example usage for the new packJvmVersionSpecificOpts
setting that allows configuring Java version-specific JVM options.

The example shows how to configure different JVM options for Java 8, 11, 17,
and 21, demonstrating common use cases like:
- MaxPermSize for Java 8 (removed in Java 9+)
- JVM compiler options for Java 11
- ZGC garbage collector for Java 17+
- Generational ZGC for Java 21+

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions github-actions bot added the doc label Sep 4, 2025
xerial and others added 8 commits September 4, 2025 13:09
- Applied scalafmt code formatting to PackPlugin.scala
- Fixed scripted test plugins.sbt to use correct plugin loading pattern
- Simplified scripted test to just run the program itself
  - Running the script validates that Java version detection doesn't break functionality
  - The generated JVM options are verified in the output

The test now passes successfully and verifies that the Java version-specific
JVM options feature works correctly.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Enhanced the scripted test to validate that the correct JVM options are
applied based on the detected Java version. The test now:

- Extracts and displays the Java major version
- Validates that base JVM options are present (-Xms256m, -Xmx512m)
- Checks for version-specific options based on the current Java version
- Fails the test if expected options are not found
- Added Java 24 configuration to demonstrate the feature working

This ensures the Java version detection and option application feature
works correctly across different Java versions.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Added support for the JDK 24+ specific option --sun-misc-unsafe-memory-access=allow
which is important for applications that need to use sun.misc.Unsafe operations.

In Java 24+, unsafe memory access is restricted by default and requires explicit
permission via this flag. This is particularly important for libraries and frameworks
that rely on unsafe operations for performance optimizations.

Changes:
- Added --sun-misc-unsafe-memory-access=allow to Java 24 configuration in test
- Updated validation logic to verify the option is correctly applied
- Updated README example to show this important Java 24+ option

This helps users properly configure their applications for Java 24+ environments
where unsafe memory access restrictions are enforced.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
The GenerationalZGC option syntax changed in Java 21. The correct flag
is now -XX:+ZGenerational instead of -XX:+GenerationalZGC. This fix
ensures compatibility with Java 21 in CI environments.

- Updated README.md example
- Fixed test configuration in jvm-version-opts test
- Updated validation logic to check for ZGenerational

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add sbt version configuration file for the min-project scripted test
to ensure consistent sbt version usage in testing.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Add note to ensure code is formatted with scalafmtAll before pushing
to maintain consistent code style across the project.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@xerial
Copy link
Copy Markdown
Owner Author

xerial commented Sep 4, 2025

/gemini review

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds a useful feature for configuring JVM options specific to Java versions. The implementation looks good, but I've found a critical issue with how options are quoted in Windows batch scripts, which would make them non-functional. I've provided a suggestion to fix this.

Additionally, I've suggested an improvement to the version matching logic. Instead of an exact match, applying options for the highest configured version that is less than or equal to the runtime Java version would be much more flexible and align better with user expectations (e.g., for "Java 11+"). I've provided a full set of changes for this enhancement across the plugin code, launch scripts, and tests.

Finally, I've pointed out a minor typo in a JVM option name in the README and test files.

PROG_VERSION: String,
PROG_REVISION: String,
JVM_OPTS: String = "",
JVM_VERSION_OPTS: Map[Int, String] = Map.empty,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation for version-specific JVM options uses an exact version match. This can be inconvenient if you want to set options for a Java version and all subsequent versions (e.g., Java 11+), as implied by the 24+ comment in the README. A more flexible approach would be to apply the options for the highest configured version that is less than or equal to the detected Java version.

To implement this, I suggest changing JVM_VERSION_OPTS from a Map[Int, String] to a sorted Seq[(Int, String)]. This will allow the launch scripts to iterate through the versions and find the best match. I'll provide related suggestions in other files to complete this change.

Suggested change
JVM_VERSION_OPTS: Map[Int, String] = Map.empty,
JVM_VERSION_OPTS: Seq[(Int, String)] = Seq.empty,

Comment on lines +127 to +131
@for((version, opts) <- opts.JVM_VERSION_OPTS) {
if [ "$JAVA_VERSION" = "@version" ]; then
JAVA_OPTS="$JAVA_OPTS @opts"
fi
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

This logic performs an exact match for the Java version. To make this feature more flexible, this should be changed to apply options for the highest configured version that is less than or equal to the current Java version. This would make configurations like "for Java 11 and newer" work as expected.

VERSION_SPECIFIC_OPTS=""
@for((version, opts) <- opts.JVM_VERSION_OPTS) {
if [ "$JAVA_VERSION" -ge "@version" ]; then
  VERSION_SPECIFIC_OPTS="@opts"
fi
}
if [ -n "$VERSION_SPECIFIC_OPTS" ]; then
  JAVA_OPTS="$JAVA_OPTS $VERSION_SPECIFIC_OPTS"
fi

Comment on lines +34 to +70
val expectedVersionOptions = majorVersion match {
case 8 =>
// Java 8 should have MaxPermSize
val hasMaxPermSize = argsList.exists(_.contains("MaxPermSize"))
println(s" Has MaxPermSize (Java 8): $hasMaxPermSize")
hasMaxPermSize
case 11 =>
// Java 11 should have UseJVMCICompiler
val hasJVMCI = argsList.exists(_.contains("UseJVMCICompiler"))
println(s" Has UseJVMCICompiler (Java 11): $hasJVMCI")
hasJVMCI
case 17 =>
// Java 17 should have UseZGC
val hasZGC = argsList.exists(_.contains("UseZGC"))
println(s" Has UseZGC (Java 17): $hasZGC")
hasZGC
case 21 =>
// Java 21 should have UseZGC and ZGenerational
val hasZGC = argsList.exists(_.contains("UseZGC"))
val hasGenZGC = argsList.exists(_.contains("ZGenerational"))
println(s" Has UseZGC (Java 21): $hasZGC")
println(s" Has ZGenerational (Java 21): $hasGenZGC")
hasZGC && hasGenZGC
case 24 =>
// Java 24 should have UseG1GC, G1HeapRegionSize, and sun-misc-unsafe-memory-access
val hasG1GC = argsList.exists(_.contains("UseG1GC"))
val hasG1HeapRegion = argsList.exists(_.contains("G1HeapRegionSize"))
val hasUnsafeMemoryAccess = argsList.exists(_.contains("sun-misc-unsafe-memory-access=allow"))
println(s" Has UseG1GC (Java 24): $hasG1GC")
println(s" Has G1HeapRegionSize (Java 24): $hasG1HeapRegion")
println(s" Has --sun-misc-unsafe-memory-access=allow (Java 24): $hasUnsafeMemoryAccess")
hasG1GC && hasG1HeapRegion && hasUnsafeMemoryAccess
case _ =>
// For other versions, no specific options are expected
println(s" No version-specific options for Java $majorVersion")
true
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the version matching logic is updated to be "greater than or equal to", this test logic will need to be updated to validate the new behavior. The test should check that for a given Java version, the options for the highest configured version less than or equal to it are applied. This suggestion also includes a fix for the GenerationalZGC option name.

    val expectedVersionOptions = majorVersion match {
      case v if v >= 24 =>
        // Java 24+ should have UseG1GC, G1HeapRegionSize, and sun-misc-unsafe-memory-access
        val hasG1GC = argsList.exists(_.contains("UseG1GC"))
        val hasG1HeapRegion = argsList.exists(_.contains("G1HeapRegionSize"))
        val hasUnsafeMemoryAccess = argsList.exists(_.contains("sun-misc-unsafe-memory-access=allow"))
        println(s"  Has UseG1GC (Java 24+): $hasG1GC")
        println(s"  Has G1HeapRegionSize (Java 24+): $hasG1HeapRegion")
        println(s"  Has --sun-misc-unsafe-memory-access=allow (Java 24+): $hasUnsafeMemoryAccess")
        hasG1GC && hasG1HeapRegion && hasUnsafeMemoryAccess
      case v if v >= 21 =>
        // Java 21-23 should have UseZGC and GenerationalZGC
        val hasZGC = argsList.exists(_.contains("UseZGC"))
        val hasGenZGC = argsList.exists(_.contains("GenerationalZGC"))
        println(s"  Has UseZGC (Java 21+): $hasZGC")
        println(s"  Has GenerationalZGC (Java 21+): $hasGenZGC")
        hasZGC && hasGenZGC
      case v if v >= 17 =>
        // Java 17-20 should have UseZGC
        val hasZGC = argsList.exists(_.contains("UseZGC"))
        println(s"  Has UseZGC (Java 17+): $hasZGC")
        hasZGC
      case v if v >= 11 =>
        // Java 11-16 should have UseJVMCICompiler
        val hasJVMCI = argsList.exists(_.contains("UseJVMCICompiler"))
        println(s"  Has UseJVMCICompiler (Java 11+): $hasJVMCI")
        hasJVMCI
      case v if v >= 8 =>
        // Java 8-10 should have MaxPermSize
        val hasMaxPermSize = argsList.exists(_.contains("MaxPermSize"))
        println(s"  Has MaxPermSize (Java 8+): $hasMaxPermSize")
        hasMaxPermSize
      case _ =>
        // For other versions, no specific options are expected
        println(s"  No version-specific options for Java $majorVersion")
        true
    }

xerial and others added 3 commits September 4, 2025 14:03
Changed the behavior of packJvmVersionSpecificOpts to apply options
as version ranges instead of exact matches. When versions are specified
(e.g., 8, 11, 17, 21, 24), the options are now applied as ranges:
- Java [8,11) uses options for version 8
- Java [11,17) uses options for version 11
- Java [17,21) uses options for version 17
- Java [21,24) uses options for version 21
- Java [24,∞) uses options for version 24

This provides more intuitive behavior where each version specification
covers all Java versions from that point until the next specified version.

- Updated launch script templates (Unix and Windows) to use >= comparison
- Options are applied by finding the highest version <= current Java version
- Updated documentation to explain the range-based behavior
- Tests updated to validate range functionality

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Addresses review feedback: JVM options were being incorrectly quoted
in Windows batch scripts, causing java.exe to receive the quotes as
part of the arguments. This would make the options non-functional.

- Remove quotes from both JVM_OPTS and JVM_VERSION_OPTS for Windows
- Unix shell scripts continue to use quotes as needed
- This is critical for Windows users to have working JVM options

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
Updated the test validation logic to match the new range-based behavior
where options apply to version ranges rather than exact matches.
For example, Java 22 or 23 will now correctly validate against the
options for Java 21, and Java 25+ will validate against Java 24 options.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@xerial xerial merged commit b64e996 into main Sep 4, 2025
6 checks passed
@xerial xerial deleted the feature/java-version-specific-opts branch September 4, 2025 21:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant